/**
 * 
 */
package edu.isistan.genetic.operators;

import edu.isistan.genetic.representations.*;

import java.util.Vector;

import org.apache.log4j.Logger;

import edu.isistan.metamodel.DataType;

/**
 * @author marcos.trotti
 *
 */
public class MutationSimpleProcess implements MutationProcess {
	
	protected Vector<Operator> parametersOperators;
	protected Vector<Operator> returningValuesOperators;
	private double probability;
	
	protected static Logger  logger = Logger.getLogger(MutationSimpleProcess.class);
	
	
	/**
	 * @param parametersOperators
	 * @param returningValuesOperators
	 * @param probability
	 */
	public MutationSimpleProcess(Vector<Operator> parametersOperators,
			Vector<Operator> returningValuesOperators, double probability) {
		super();
		this.parametersOperators = parametersOperators;
		this.returningValuesOperators = returningValuesOperators;
		this.probability = probability;
	}
	
	private Vector<Operator> getSupportedOperators(DataType aDataType, boolean isReturningValue){
		Vector<Operator> aOperators=new Vector<Operator>();
		if (isReturningValue){
			for (Operator operator : this.returningValuesOperators) {
				if (operator.isCompatible(aDataType))
					aOperators.add(operator);
			}
		}else{
			for (Operator operator : this.parametersOperators) {
				if (operator.isCompatible(aDataType)){
					aOperators.add(operator);
				}
			}
		}
		return aOperators;
	}

	protected int operatorSelectionStrategy(Vector<Operator> operators, boolean returning){
		return (int)(Math.random() * operators.size() );
	}
	/**
	 * Mutation process only encapsulate if aUnEncapsulatedParameters size is grater than 1
	 * @param aDataTypes
	 * @param returning
	 * @return
	 */
	private Vector<DataType> mutateDataTypes(Vector<DataType> aDataTypes,boolean returning){
		Vector<DataType> aMutatedParameters= new Vector<DataType>();
		Vector<DataType> aUnEncapsulatedParameters = new Vector<DataType>();
		Encapsulate encapsulationOperator=null;
		for (DataType dataType : aDataTypes) {
			double prob=Math.random();
			if (prob <= this.probability){
				Vector<Operator> supported= this.getSupportedOperators(dataType, returning);
				Vector<DataType> aParameters=new Vector<DataType>();
				aParameters.add(dataType);
				if (!supported.isEmpty()){
					Operator aOperator=supported.get(operatorSelectionStrategy(supported,returning));
					logger.info("Mutating " + dataType.getName() + " applying operator " + aOperator);
					if ((!returning) && (aOperator.isEncapsulationCompatible())){//Process encapsulated operator
						encapsulationOperator = (Encapsulate)aOperator;
						aUnEncapsulatedParameters.add(dataType);
					}else
						aMutatedParameters.addAll(aOperator.mutate(aParameters));
				}else
					aMutatedParameters.add(dataType);
			}else
				aMutatedParameters.add(dataType);
		}
		if (aUnEncapsulatedParameters.size()>1)
			aMutatedParameters.addAll(encapsulationOperator.mutate(aUnEncapsulatedParameters));
		else
			aMutatedParameters.addAll(aUnEncapsulatedParameters);
		return aMutatedParameters;
	}
	
	/* (non-Javadoc)
	 * @see operators.MutationProcess#mutate(representation.Individual)
	 */
	
	public Individual mutate(Individual aIndividual) {
		
		int i=0;
		logger.info("Mutating Individual using " + this.getClass().getCanonicalName());
		for (Vector<DataType> parameters : aIndividual.getOperationsParameters()) {
			if (parameters!=null)//Puede ocurrir que no tenga parámetros
				logger.info("Mutating operation "+aIndividual.getOperationName(i));
				aIndividual.setParameterTo(this.mutateDataTypes(parameters, false), i);
			i++;
		}
		//Returning values
		logger.info("Mutating return types");
		aIndividual.setReturningValue(this.mutateDataTypes(aIndividual.getReturningValues(), true));
		logger.info("End Mutating Individual");
		return aIndividual;
	}

	@Override
	public String toString() {
		StringBuffer sb=new StringBuffer();
		sb.append("Process Name MutationSimpleProcess \n");
		sb.append("Mutation Probability: "+ probability + "\n");
		sb.append("Parameters Operators \n");
		for (Operator op : parametersOperators) {
			sb.append(op.toString() + "\n");
		}
		sb.append("Returning Values Operators \n");
		for (Operator op : returningValuesOperators) {
			sb.append(op.toString() + "\n");
		}
		return sb.toString();
	}

}
